package tech.yixiyun.framework.kuafu.boot.server.tomcat;

import cn.hutool.core.util.ArrayUtil;
import com.fasterxml.jackson.databind.JsonNode;
import tech.yixiyun.framework.kuafu.config.AppConfig;
import tech.yixiyun.framework.kuafu.config.ConfigKey;
import tech.yixiyun.framework.kuafu.kits.JSONKit;
import tech.yixiyun.framework.kuafu.log.LOGGER;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.tomcat.JarScanFilter;
import org.apache.tomcat.util.descriptor.web.ErrorPage;
import org.apache.tomcat.util.scan.StandardJarScanFilter;

import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;

public class TomcatStarter {

    private  static TomcatStarter INSTANCE ;
    static {
        INSTANCE = new TomcatStarter();
    }

    /**
     * 正在运行的Tomcat服务器实例
     */
    private Tomcat tomcatInstance;

    /**
     * 项目目录
     */
    private String rootPath;

    /**
     * catalina的路径，一般会放临时文件和work文件
     */
    private String tomcatBasePath;
    /**
     * 项目部署的ContextPath
     */
    private String contextPath;


    /**
     * 项目的资源路径
     */
    private String docPath;

    /**
     * 项目类加载路径
     */
    private String[] classPathes;

    /**
     * 当前项目目录是否是maven结构
     */
    private boolean isMaven;


    /**
     * 监听请求的端口
     */
    private Integer port;

    /**
     * 链接器使用的协议
     */
    private String protocol;

    /**
     * 监听来自哪儿的请求，默认是来自所有网卡的请求
     */
    private String listenAddr;

    /**
     * Host实例
     */
    private Host host;


    /**
     * 项目的Context实例
     */
    private StandardContext context;

    private TomcatStarter() {
        try {

            initServer();

        } catch (Exception e) {
            LOGGER.error("服务启动失败", e);
            throw new RuntimeException(e);

        }
    }



    /**
     * 启动
     */
    public static void start() {

        try {

            INSTANCE.tomcatInstance.start();

            //注册关闭钩子
            Runtime.getRuntime().addShutdownHook(new TomcatCloser(INSTANCE));

            String ip = AppConfig.getAsString(ConfigKey.SERVER_LISTEN);
            LOGGER.infoTitle("^^^ 应用启动成功 ^^^ 启动地址：http://" +(Objects.equals(ip, "*") ? "localhost" : ip)+ ":" + INSTANCE.port + INSTANCE.contextPath);
        } catch (Exception e) {
            LOGGER.error("服务启动失败", e);
            INSTANCE.shutdown();
        }
        INSTANCE.tomcatInstance.getServer().await();
    }

    /**
     * 初始化服务器
     *
     * @throws Exception
     */
    private void initServer() throws Exception {

        tomcatInstance = new Tomcat();
        LOGGER.info("Java Version：" + System.getProperty("java.version"));

        analyseRootPath();

        initTomcatBasePath();




        //创建server实例 和 service实例
        tomcatInstance.getServer();

        initConnector();

        initHost();

        initWebapp();


        LOGGER.info("准备启动Tomcat Server, 端口: {}, ContextPath: {}", port, contextPath);


        initEnv();

    }

    /**
     * 初始化某些环境变量
     */
    private void initEnv() {
        //启动报错就停止
        System.setProperty("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE", "true");
        //改变随机数生成器使用的熵池，使用非阻塞的
        System.setProperty("java.security.egd", "file:/dev/urandom");

    }

    private void initWebapp() {
        File docFolder = null;
        if (isMaven) {
            docFolder = new File(this.rootPath, "src/main/webapp/");
            if (docFolder.exists() == false) {
                docFolder = new File(this.rootPath, "src/main/web");
            }
        } else {
            docFolder = new File(this.rootPath, "webapp");
            if (docFolder.exists() == false) {
                docFolder = new File(this.rootPath, "web");
            }
        }
        if (docFolder.exists() == false) {
            throw new RuntimeException("无法找到应用的webContent路径，服务启动失败。系统会自动尝试加载根路径下的src/main/webapp路径和webapp路径，但目前都加载失败。");
        }
        docPath = docFolder.toURI().getPath();

        this.contextPath = AppConfig.getAsString(ConfigKey.SERVER_CONTEXTPATH, "");
        context = (StandardContext) tomcatInstance.addWebapp(this.contextPath.equals("/") ? "" : this.contextPath, docPath);
        //将servlet执行过程中的 system.out和system.err输出的信息转到tomcat logger输出
        context.setSwallowOutput(true);

        setContextDefaultPages();


        LOGGER.info("Server Doc Path: {}", this.docPath);

        //让context忽略jar包扫描，加快启动速度
        JarScanFilter jarScanFilter = context.getJarScanner().getJarScanFilter();
        if (jarScanFilter != null && jarScanFilter instanceof StandardJarScanFilter) {
            StandardJarScanFilter filter = (StandardJarScanFilter) jarScanFilter;
            filter.setPluggabilitySkip("*.jar");
            filter.setTldSkip("*.jar");
        }


        analyseClassPath();

    }

    /**
     * 设置应用的一些默认页面，比如欢迎页、错误页
     */
    private void setContextDefaultPages() {

        JsonNode node = AppConfig.getAsTreeNode(ConfigKey.SERVER_ERRORPAGES);
        node.fields().forEachRemaining(item -> {
            ErrorPage page = new ErrorPage();
            page.setErrorCode(Integer.parseInt(item.getKey()));
            page.setLocation(item.getValue().asText());
            context.addErrorPage(page);
        });
//        node.elements().forEachRemaining(item -> {
//            item.fields().forEachRemaining(entry -> {
//                ErrorPage page = new ErrorPage();
//                page.setErrorCode(Integer.parseInt(entry.getKey()));
//                page.setLocation(entry.getValue().asText());
//                context.addErrorPage(page);
//            });
//        });
    }

    private void initHost() {
        this.host = tomcatInstance.getHost();
        host.setAutoDeploy(false);
    }

    /**
     * 针对tomcat创建的catalina目录
     */
    private void initTomcatBasePath() {
        if (isMaven) {
            this.tomcatBasePath = new File(this.rootPath, "target/catalina").toURI().getPath();
        } else {
            this.tomcatBasePath = new File(this.rootPath, "catalina").toURI().getPath();
        }

        System.setProperty(Globals.CATALINA_HOME_PROP, this.tomcatBasePath);
    }

    /**
     * 分析项目根目录
     */
    private void analyseRootPath() {
        // classes文件夹
        String path = Thread.currentThread().getContextClassLoader().getResource("").getFile();
        //如果父级文件夹为WEB-INF，那么就认为是生产环境，根目录就是WEB-INF的父文件夹的父文件夹
        File parentFile = new File(path).getParentFile();

        if (parentFile.getName().equals("target")) {
            //父文件夹是target 就认为是maven结构开发环境，以项目文件夹为根目录(target的父级)
            isMaven = true;
            rootPath = parentFile.getParentFile().toURI().getPath();
        } else {
            //生产环境
            rootPath = parentFile.toURI().getPath();
            return;
        }



    }

    /**
     * 初始化Connector
     */
    private void initConnector() {
        this.port = AppConfig.getAsInt(ConfigKey.SERVER_PORT, 8080);
        this.protocol = AppConfig.getAsString(ConfigKey.SERVER_PROTOCOL, "HTTP/1.1");
        String encoding = AppConfig.getAsString(ConfigKey.URI_ENCODING, "UTF-8");


        Connector connector = new Connector(protocol);
        connector.setPort(port);
        connector.setURIEncoding(encoding);
        ProtocolHandler protocolHandler = connector.getProtocolHandler();

        if (protocolHandler instanceof AbstractProtocol) {
            AbstractProtocol protocol = (AbstractProtocol)protocolHandler;
            Integer timeout = AppConfig.getAsInt(ConfigKey.SERVER_CONNECTION_TIMEOUT, 60000);
            protocol.setConnectionTimeout(timeout);
            listenAddr = AppConfig.getAsString(ConfigKey.SERVER_LISTEN, "*");
            if (listenAddr.equals("*") == false) {
                try {
                    protocol.setAddress(InetAddress.getByName(listenAddr));
                } catch (UnknownHostException e) {
                    throw new RuntimeException(e);
                }
            }
            Integer count = AppConfig.getAsInt(ConfigKey.TOMCAT_MIN_PROCESSTHREAD_COUNT, 10);
            protocol.setMinSpareThreads(count);
            count = AppConfig.getAsInt(ConfigKey.TOMCAT_MAX_PROCESSTHREAD_COUNT, 200);
            protocol.setMaxThreads(count);
            count = AppConfig.getAsInt(ConfigKey.TOMCAT_MAX_WAIT_COUNT, 100);
            protocol.setAcceptCount(count);

        }

        if (protocolHandler instanceof AbstractHttp11Protocol) {
            AbstractHttp11Protocol protocol = (AbstractHttp11Protocol)protocolHandler;
            protocol.setCompression(AppConfig.getAsString(ConfigKey.SERVER_TEXT_COMPRESS, "off"));

        }


        tomcatInstance.getService().addConnector(connector);
    }




    /**
     * 设置类的加载路径，开发环境会去查找target/classes路径
     * 生产环境会去查找 classes路径
     */
    private void analyseClassPath() {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        context.setParentClassLoader(contextClassLoader);

        File classFolder = null;
        if (isMaven) {
            classFolder = new File(this.rootPath, "target/classes");
        } else {
            classFolder =  new File(this.rootPath, "classes");
        }

        if (classFolder.exists() == false) {
            throw new RuntimeException("无法找到服务的classes路径，服务启动失败。系统会自动尝试加载根路径下的target/classes路径和classes路径，但目前都加载失败。");
        }

        String classPath = classFolder.toURI().getPath();
        WebResourceRoot resources = new StandardRoot(context);
        WebResourceSet resourceSet = new DirResourceSet(resources, "/WEB-INF/classes", classPath, "/");
        resources.addPreResources(resourceSet);

        this.classPathes = new String[]{classPath};

        if (isMaven) { //将test下的文件也加入环境中
            classFolder = new File(this.rootPath, "target/test-classes");
            if (classFolder.exists()) {
                classPath = classFolder.toURI().getPath();
                resourceSet = new DirResourceSet(resources, "/WEB-INF/classes", classPath, "/");
                resources.addPreResources(resourceSet);
                this.classPathes = ArrayUtil.append(classPathes, classPath);
            }
        }

        context.setResources(resources);
        LOGGER.info("Server Class Path: {}", JSONKit.toJson(this.classPathes));
    }



    /**
     * 关闭应用
     */
    public static void shutdown() {
        if (INSTANCE.tomcatInstance != null) {
            try {
                INSTANCE.tomcatInstance.stop();
            } catch (LifecycleException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 获取服务实例
     * @return
     */
    public static Tomcat getTomcatInstance() {
        return INSTANCE.tomcatInstance;
    }

    public static String getTomcatBasePath() {
        return INSTANCE.tomcatBasePath;
    }

    public static Integer getPort() {
        return INSTANCE.port;
    }

    public static String getProtocol() {
        return INSTANCE.protocol;
    }

    public static Host getHost() {
        return INSTANCE.host;
    }

    public static String getContextPath() {
        return INSTANCE.contextPath;
    }

    public static String getListenAddr() {
        return INSTANCE.listenAddr;
    }

    /**
     * 获取应用的根路径
     *
     * @return
     */
    public static String getRootPath() {
        return INSTANCE.rootPath;
    }

    /**
     * 获取项目的资源路径
     * @return
     */
    public static String getDocPath() {
        return INSTANCE.docPath;
    }

    /**
     * 获取项目的类加载路径
     * @return
     */
    public static String[] getClassPathes() {
        return INSTANCE.classPathes;
    }

    /**
     * 获取tomcat服务的上下文 context
     * @return
     */
    public static StandardContext getContext() {
        return INSTANCE.context;
    }
}
